# -*- coding: utf-8 -*-
import threading
import redis
import time
from collections import abc, UserDict
from typing import Union, Any
import keyword
import json

from contract_market.initialize.redis import OPRedis
from contract_market.initialize.config import InitConfig # NOQA

"""
Function:

1. Initialize write configuration to Redis
2. Read the Redis configuration periodically to update the status
---
cache:
    type : hash
    symbol : {
        key: value,
        desc: doc
    }
"""

def map_helper(data :dict, key=None):
    for k, v in data.items():
        if key:
            data[k] = key(k, v)
    return data

def map_chain(data: dict):
    for k, v in data.items():
        if isinstance(v, dict):
            data[k] = v["value"]

    return data


class jsonUnMarshal(UserDict):

    """
    ::param cfg : configuration information
    ::param client: Redis connection
    """

    def __new__(cls, data):
        """ Custom construction """
        
        if isinstance(data, abc.Mapping):
            return super().__new__(cls)
        elif isinstance(data, abc.MutableSequence):
            return [cls(item) for item in data]
        return data

    def __init__(self, cfg:abc.Mapping=None, client:redis.Redis=None) -> None:
        """ """
        if not isinstance(cfg, abc.Mapping):
            raise AttributeError("Type error not dict")
        self.data = cfg
        for key, value in cfg.items():
                if keyword.iskeyword(key):
                    key += "_"
                self.data[key] = value

    def __getattr__(self, name):
        
        if hasattr(self.data, name):
            return getattr(self.data, name)
        else:
            try:
                # 处理特殊情况
                return jsonUnMarshal(self.data[name])
            except KeyError as e:
                raise AttributeError(e) from e


class DynamicAllocation:

    """
    If a key is required, 
    ensure that the key is the same as the process name. 

    For example, 
    the PM2 --name parameter must be the same as the key to obtain the configuration information
    This is only for PM2. 
    
    If Docker is used, 
    the parameter value of name should be the same as key. 
    
    Notice:
        Special attention should be paid to: this is only for PM2
        If configuration information exists in the cache, the cache is cached by default.
        If you do not save how to prioritize file configuration,
        Configuration priority: Cache Configuration > File Configuration
    Usage:
        Make sure the input parameter contains the process name and the market name
        process name : Used to initialize cache data and load cache data
        market name : Just the initial solution
    Advice:
        The program supports command line parameter passing

    ::param cfg : configuration information
    ::param client: Redis connection
    ::param constant : description
    """
    CACHE_KEY = "{process_name}:{symbol_name}"
    INTERVAL = 10 # The configuration is updated every 10 seconds
    def __init__(self, cfg:InitConfig=None, constant:object=None) -> None:
        """ """
        self._client: redis.Redis = OPRedis()
        self._lock = threading.Lock()
        self.constant = constant
        self.key = self.CACHE_KEY.format(process_name=cfg.argv.process_name, symbol_name=cfg.argv.symbol_name)
        t = threading.Thread(target=self.timer, daemon=True, name="DynamicAllocation")
        t.start()

        jsonObj = self.to_cache(cfg.resolve(), cfg.argv.symbol_name)
        self.jsonObj = jsonUnMarshal(jsonObj)

    def _merge(self, _k, _v):
        """ """
        if not hasattr(self.constant, _k):
            return _v
        dic = dict()
        dic["value"] = _v
        dic["desc"] = getattr(self.constant, _k)
        return dic

    def to_cache(self, data, symbol):
        """ """
        value = self._client.get(self.key)
        data = map_helper(data[symbol], self._merge)
        self._client.set(self.key, json.dumps(data))
        '''
        if not value:
            data = map_helper(data[symbol], self._merge)
            self._client.set(self.key, json.dumps(data))
        else:
            data = self.from_redis_opt(self.key)
        '''
        return data

    def values(self, key=None) ->  Union[dict, Any]:
        if key:
            v = self.jsonObj[key]
            return v['value'] if isinstance(v, dict) else v
        else:
            return map_chain(self.jsonObj)

    def from_redis_opt(self, key):
        """"""
        cache = self._client.get(key)
        data = json.loads(cache.decode("utf8"))
        return data

    def timer(self):
        while True:
            time.sleep(self.INTERVAL)
            # 从redis读取
            with self._lock:
                self.jsonObj = jsonUnMarshal(self.from_redis_opt(self.key))